cmake_minimum_required(VERSION 3.17)

set(project_languages CXX)
if(NOT DEFINED YGG_BRUTE_ENGINES)
    set(YGG_BRUTE_ENGINES "cuda" "opencl")
endif()

set(has_cuda OFF)
set(has_opencl OFF)

if("cuda" IN_LIST YGG_BRUTE_ENGINES)
    set(has_cuda ON)
endif()

if("opencl" IN_LIST YGG_BRUTE_ENGINES)
    set(has_opencl ON)
endif()

if(${has_cuda})
    list(APPEND project_languages CUDA)

    set(CMAKE_CUDA_STANDARD 17)
    set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -O3 -Xptxas -O4 -Xptxas -v")
    if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
        set(CMAKE_CUDA_ARCHITECTURES 61)
    endif()
endif()

project(ygg-brute LANGUAGES ${project_languages})

set(src_dir ${CMAKE_SOURCE_DIR}/src)
set(third_party_dir ${CMAKE_SOURCE_DIR}/third_party)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")

add_executable(ygg-brute main.cpp)
target_include_directories(ygg-brute PRIVATE ${src_dir} )

if(${has_cuda})
    add_library(cuda-engine STATIC ${src_dir}/cuda/engine.cu)
    target_include_directories(cuda-engine PRIVATE ${src_dir} ${third_party_dir})
    target_link_libraries(ygg-brute PRIVATE cuda-engine)
    set_property(TARGET ygg-brute APPEND PROPERTY COMPILE_DEFINITIONS CUDA_ENGINE)
endif()

if(${has_opencl})
    find_package(OpenCL REQUIRED)

    set(kernel_file ${CMAKE_BINARY_DIR}/kernel.cl)
    set(kernel_include ${CMAKE_BINARY_DIR}/kernel.cl.h)
    set(src_dir ${CMAKE_SOURCE_DIR}/src)

    FILE(GLOB kernel_src_files ${src_dir}/opencl/kernel/*)
    FILE(GLOB generic_src_files ${src_dir}/generic/*)
    FILE(GLOB generic_kernel_src_files ${src_dir}/generic/kernel/*)

    FILE(RELATIVE_PATH rel_kernel_file ${CMAKE_BINARY_DIR} ${kernel_file})
    add_custom_command(
        OUTPUT ${kernel_file}
        COMMAND cpp -I ${src_dir} -I ${src_dir}/opencl/kernel ${src_dir}/opencl/kernel/kernel.cl -o ${kernel_file}
        DEPENDS ${kernel_src_files} ${generic_src_files} ${generic_kernel_src_files}
        COMMENT "Generating preprocessed kernel ${kernel_file}")

    FILE(RELATIVE_PATH rel_kernel_include ${CMAKE_BINARY_DIR} ${kernel_include})
    add_custom_command(
        OUTPUT ${kernel_include}
        COMMAND xxd -i ${rel_kernel_file} ${kernel_include}
        DEPENDS ${rel_kernel_file}
    )

    add_custom_target(opencl-kernel DEPENDS ${kernel_include})

    add_library(opencl-engine STATIC ${src_dir}/opencl/engine.cpp)
    target_include_directories(opencl-engine PRIVATE ${src_dir} ${third_party_dir} ${CMAKE_BINARY_DIR})
    target_link_libraries(opencl-engine PRIVATE OpenCL::OpenCL)
    add_dependencies(opencl-engine opencl-kernel)
    target_link_libraries(ygg-brute PRIVATE opencl-engine)
    get_target_property(opencl_engine_include_directories opencl-engine INCLUDE_DIRECTORIES)
    find_file(clhpp NAMES CL/cl2.hpp PATHS ${opencl_engine_include_directories})
    if (${clhpp} STREQUAL "clhpp-NOTFOUND")
        message(FATAL_ERROR "OpenCL C++ headers are required but not found")
    endif()
    set_property(TARGET ygg-brute APPEND PROPERTY COMPILE_DEFINITIONS OPENCL_ENGINE)
endif()

# TESTS
enable_testing()

if(${has_cuda})
    add_executable(cuda-generator-test test/cuda_generator_test.cpp)
    target_include_directories(cuda-generator-test PRIVATE ${src_dir} test)
    target_link_libraries(cuda-generator-test PRIVATE cuda-engine)
    set_target_properties(cuda-generator-test PROPERTIES LINK_FLAGS -lsodium)
    add_test("CUDA-generator" cuda-generator-test)
endif()

if(${has_opencl})
    add_executable(opencl-generator-test test/opencl_generator_test.cpp)
    target_include_directories(opencl-generator-test PRIVATE ${src_dir} test)
    target_link_libraries(opencl-generator-test PRIVATE opencl-engine)
    set_target_properties(opencl-generator-test PROPERTIES LINK_FLAGS -lsodium)
    add_test("OpenCL-generator" opencl-generator-test)
endif()
